/*
* Copyright 2011 Pavel Kozhin 
*
* Licensed under the Apache License, Version 2.0 (the "License"); 
* you may not use this file except in compliance with the License. 
* You may obtain a copy of the License at 
* 
* http://www.apache.org/licenses/LICENSE-2.0
* 
* Unless required by applicable law or agreed to in writing, software 
* distributed  under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 
* See the License for the specific language governing permissions and 
* limitations under the License.
*
* Author: Pavel Kozhin.
*/
package org.etherframework.core.manager
{
	import flash.errors.IllegalOperationError;
	import flash.events.EventDispatcher;
	import flash.events.IEventDispatcher;
	import flash.system.ApplicationDomain;
	
	import mx.core.FlexGlobals;
	import mx.core.IFactory;
	import mx.managers.ISystemManager;
	
	import org.etherframework.IEther;
	import org.etherframework.common.command.ICommands;
	import org.etherframework.common.mediation.IMediations;
	import org.etherframework.common.wire.IWiring;
	import org.etherframework.core.$Globals;
	import org.etherframework.core.EtherConfig;
	import org.etherframework.core.GlobalConfig;
	import org.etherframework.core.IModule;
	import org.etherframework.core.bridge.Bridge;
	import org.etherframework.core.bridge.BridgeConfig;
	import org.etherframework.core.bridge.BridgeFacades;
	import org.etherframework.core.bridge.BridgeInteraction;
	import org.etherframework.core.bridge.BridgeProcessor;
	import org.etherframework.core.bridge.Controllers;
	import org.etherframework.core.bridge.IBridge;
	import org.etherframework.core.bridge.IObjectController;
	import org.etherframework.core.common.IControllerFactory;
	import org.etherframework.core.common.IInstanceFactory;
	import org.etherframework.core.common.IObjectProcessor;
	import org.etherframework.core.common.RootDispatcher;
	import org.etherframework.core.common.SuperFactory;
	import org.etherframework.core.context.Context;
	import org.etherframework.core.context.Contexts;
	import org.etherframework.core.context.Default;
	import org.etherframework.core.module.IModuleContainer;
	import org.etherframework.core.module.IModulePart;
	import org.etherframework.core.module.ModuleConfig;
	import org.etherframework.core.module.ModuleContainer;
	import org.etherframework.core.module.ModuleController;
	import org.etherframework.core.utils.ClassUtils;
	import org.etherframework.core.utils.Dispatcher;
	import org.etherframework.dependency.IDependencies;
	import org.etherframework.mediator.IMediators;
	
	[ExcludeClass]
	public class EtherManagerImpl implements IEtherManager, IControllerFactory, IInstanceFactory
	{
		internal var systemManager:ISystemManager;
		
		private var _config:EtherConfig;
		private var _dispatcher:IEventDispatcher = new EventDispatcher();
		
		private var _initialized:Boolean = false;
		private var _moduleContainer:IModuleContainer;
		private var _applicationDomain:ApplicationDomain;
		private var _rootDispatcher:RootDispatcher;
		private var _instanceFactory:IInstanceFactory;
		private var _objectProcessors:Vector.<IObjectProcessor>;
		private var _destructionManager:DestructionManager;
		
		public function initialize(config:EtherConfig):void
		{
			if (!config)
				throw new ArgumentError("Argument config:EtherConfig can not be null.");
			if (_initialized)
				throw new IllegalOperationError("IEtherManager is already initialized.");
			
			_initialized = true;
			_config = config;
			
			init();
		}
		
		public function attach(moduleContext:IModule):void
		{
			ensureInitialized();
			
			if (!moduleContext)
				throw new ArgumentError("Argument context:IModule can not be null.");
			
			if (!moduleContext.initialized)
				_moduleContainer.addController(initModule(moduleContext));
			
			var controller:IObjectController = _moduleContainer.hasController(moduleContext);
			
			if (!controller)
				throw new IllegalOperationError("IContext does not have controller.");
			
			if (controller.detached)
			{
				controller.attach();
				controller.detached = false;
			}
		}
		
		public function destroy(module:IModule):void
		{
			ensureInitialized();
			
			if (!module)
				throw new ArgumentError("Argument module:IModule can not be null.");
			
			var controller:IObjectController = 
				_moduleContainer.hasController(module); 
			
			if (!controller)
				throw new IllegalOperationError("IContext does not have controller.");
			
			_destructionManager.destroy(controller, destroyedHandler);
		}
		
		protected function initModule(module:IModule):IObjectController
		{
			var result:IObjectController;
			
			const config:BridgeConfig = new BridgeConfig();
			const gConfig:GlobalConfig = $Globals.config;
			const bridge:Bridge = new Bridge();
			
			config.module = module;
			config.config = _config.clone();
			config.defaultContext = new Default(null);
			config.contexts = new Contexts();
			config.facade = new BridgeFacades(new Default(null), config.contexts);
			config.localDispatcher = new EventDispatcher();
			config.globalDispatcher = _dispatcher;
			config.dispatcher = new Dispatcher(config.localDispatcher);
			config.interaction = new BridgeInteraction();
			config.applicationDomain = _applicationDomain;
			config.instanceFactory = _instanceFactory;
			config.destructionManager = _destructionManager;
			config.controllers = new Controllers(bridge);
			config.processor = new BridgeProcessor(config.facade);

			BridgeInteraction(config.interaction).initialize(bridge, config.dispatcher);
			config.processor.initialize(_instanceFactory, _objectProcessors);
			bridge.initialize(config);
			
			const ether:IEther = bridge.createEther();
			const moduleConfig:ModuleConfig = createModuleConfig(bridge, ether, gConfig);
			config.module.initialize(moduleConfig);
			BridgeFacades(config.facade).initialize(config.module, bridge, moduleConfig);
			result = IObjectController(bridge.process(config.module, ether, this));
			
			return result;
		}
		
		protected function init():void
		{
			const config:GlobalConfig = $Globals.config;
			const superFactory:SuperFactory = config.superFactory;
			var factory:IFactory = superFactory.createFactory(DestructionManager);
			
			_applicationDomain = systemManager.loaderInfo.applicationDomain;
			_destructionManager = DestructionManager(factory.newInstance());
			
			factory = superFactory.createFactory(RootDispatcher);
			_rootDispatcher = RootDispatcher(factory.newInstance());
			
			if (!_rootDispatcher.initialized)
				_rootDispatcher.initialize(IEventDispatcher(FlexGlobals.topLevelApplication));
			
			factory = superFactory.createFactory(ModuleContainer);
			_moduleContainer = ModuleContainer(factory.newInstance());
			ModuleContainer(_moduleContainer).initialize(_rootDispatcher);
			_instanceFactory = config.instanceFactory || this;
			_objectProcessors = config.getObjectProcessors();
		}
		
		public function createController(target:Object, ether:IEther, bridge:IBridge):IObjectController
		{
			ensureInitialized();
			
			return new ModuleController(IModule(target), ether, bridge);
		}
		
		public function createInstance(generator:Class, args:Array, ether:IEther):Object
		{
			return ClassUtils.instantiate(generator, args);
		}
		
		private function ensureInitialized():void
		{
			if(!_initialized)
				throw new Error("IEtherManager should be initialized.");
		}
		
		private function destroyedHandler(item:IObjectController):void
		{
			if (item.destroyed)
				_moduleContainer.removeController(item);
		}
		
		private function createModuleConfig(bridge:IBridge, ether:IEther, 
											config:GlobalConfig):ModuleConfig
		{
			var dependencies:IDependencies;
			var mediators:IMediators;
			var wiring:IWiring;
			var commands:ICommands;
			var mediations:IMediations;
			var context:Context = new Default();
			
			if(config.dependenciesFactory)
			{
				dependencies = config.dependenciesFactory.newInstance(context) as IDependencies;
				IModulePart(dependencies).init(bridge);
			}
			if (config.mediatorsFactory)
			{
				mediators = config.mediatorsFactory.newInstance(context) as IMediators;
				IModulePart(mediators).init(bridge);
			}
			if (config.wiringFactory)
			{
				wiring = config.wiringFactory.newInstance(context) as IWiring;
				IModulePart(wiring).init(bridge);
			}
			if (config.commandsFactory)
			{
				commands = config.commandsFactory.newInstance(context) as ICommands;
				IModulePart(commands).init(bridge);
			}
			if (config.mediationFactory)
			{
				mediations = config.mediationFactory.newInstance(context) as IMediations;
			}
			
			return new ModuleConfig(bridge, ether, dependencies, 
				mediators, wiring, commands, mediations);
		}
		
		public function get config():EtherConfig 
		{ 
			ensureInitialized(); 
			return _config.clone() 
		}
	}
}